﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using xplang.run;
using xplang.abstractions;
using xplang.errors;
using xplang.statements;
using xplang.contexts;

namespace xplang.parse
{
  public class CompilerForClass : CompilerForFiles
  {
    public CompilerForClass(SourceReader source, Context context, Token name)
      : base(source, new CompiledClass(context, name.text))
    {
      this.declaringClass = (CompiledClass)declaring;
      context.registerType(name, declaringClass);
    }

    public readonly CompiledClass declaringClass;

    public override void compileRequirements()
    {
      List<GenericParamDecl> genericParams = new List<GenericParamDecl>();
      if (isOperator("<"))
      {
        nextToken();
        while (!isOperator(">"))
        {
          Token name = readIdentifier();
          ClassRef extends = null;
          if (isClause("extends"))
          {
            nextToken();
            extends = compileTypeReference(declaringClass);
          }
          else
            extends = xplang.rtl.RTL.objectClass.getRef(null);
          genericParams.Add(new GenericParamDecl(name.text, extends));
        }
        checkOperator(">");
        nextToken();
      }
      List<ClassRef> inheritance = new List<ClassRef>();
      if (isOperator(":"))
      {
        do
        {
          nextToken();
          ClassRef r = compileTypeReference(declaringClass);
          if (r == null)
            new TypeExpectedError(current);
          inheritance.Add(r);
        } while (isOperator(","));
      }
      declaringClass.genericParams = genericParams.ToArray();
      declaringClass.inheritance = inheritance.ToArray();
    }

    public override void compileDeclarations()
    {
      if (!readOperator("{"))
        return;
      while (!isEof() && !isOperator("}"))
      {
        Modifier modifiers = compileModifiers();
        ClassRef type = null;
        if (!tryCompileTypeReference(declaringClass, ref type))
        {
          new TypeExpectedError(current);
          nextToken();
          continue;
        }
        Token name = current;
        nextToken();
        if (name.type == TokenType.ttFunction)
        {
          compileMethod(modifiers, type, name);
        }
        else if (name.type == TokenType.ttIdentifier)
        {
          if (isOperator(";"))
          {
            if (declaringClass.findAttribute(name.text, true) != null)
              new DuplicateElementError(name);
            declaringClass.attributes.Add(new Variable(Modifier.None, name.text, type, null));
            nextToken();
          }
          else if (isOperator("="))
            compileAttribute(modifiers, type, name);
          else
          {
            new MissingClassMemberError(current);
            break;
          }
        }
        else
        {
          new MissingClassMemberError(current);
          break;
        }
      }
      readOperator("}");
      checkEof(true);
    }

    private void compileAttribute(Modifier modifiers, ClassRef type, Token name)
    {
      if (declaringClass.findAttribute(name.text, true) != null)
        new DuplicateElementError(name);
      Variable attr = new Variable(Modifier.None, name.text, type, null);
      declaringClass.attributes.Add(attr);
      checkOperator("=");
      nextToken();
      assignImplementation(null, ";", false,
        delegate(ImplementationCode c)
        {
          return new CompilerForExpression(declaringClass, null, name, attr.type, null, c, CompilerForExpressionSupport.ExpressionWithoutAttribs());
        },
        delegate(ImplementationCode c)
        {
          attr.init = (Expression)(c.code);
        }
        );
    }

    private void compileMethod(Modifier modifiers, ClassRef returnType, Token name)
    {
      CompiledClassMethod method = new CompiledClassMethod(declaringClass, modifiers, returnType, name.text, compileMethodParams(name));

      ClassRef[] paramTypes = (from p in method.parameters
                               select p.type).ToArray();

      // todo virtual / override
      if (declaringClass.findMethod(name.text, paramTypes, null, true) != null)
        new DuplicateElementError(name);

      declaringClass.methods.Add(method);
      compileMethodImplementation(declaringClass, name, method, CompilerForExpressionSupport.MethodStatement());
    }

    public override void compileImplementations()
    {
      int n = implementationCodes.Count;
      for (int i = 0; i < n; i++)
        implementationCodes[i].compile();
    }
  }
}
